JS定时器遇浏览器调度踩坑日记

您所在的位置:网站首页 js 浏览器最小化 JS定时器遇浏览器调度踩坑日记

JS定时器遇浏览器调度踩坑日记

#JS定时器遇浏览器调度踩坑日记| 来源: 网络整理| 查看: 265

业务场景

在线状态管理

业务描述

前端:处于 在线,忙碌 状态的时候需要定时调用后台的心跳接口,和后台设定的说10秒一次的间隔,这个接口定义为工作人员是否在线的心跳。这个接口后台返回了 计费,使用权限,以及当前是否在线标识。因不可抗力原因未能使用上socket。

后台:每隔一分钟扫描一次心跳表,获取当前工作人员的在线状态,得知在线状态来做对应的在线分配接入等业务。

代码实现

实现方式上,考虑过:setTimeout,setInterval,requestAnimationFrame,咱货比三家

setTimeout:去消息队列排队的时候只需排一次队。

setTimeout(()=>{ console.log(1) }, 500) setTimeout(()=>{ console.log(2) }, 1000) setTimeout(()=>{ console.log(3) }, 1500) alert(4)

setInterval:需要按照预设的间隔时间,每到时间点都去排一下;setInterval去排队时,如果发现自己还在队列中未执行,则不会再去队列排队。也就是说,同一个inerval,在队列里只会有一个。

当然,这个地方执行三次 setInterval 也会有三次执行,不过就有了三个定时器了。 setInterval(()=>{ console.log('执行') }, 500) alert(1)

requestAnimationFrame:可以通过模拟实现 setTimeout,setInterval

最终的代码实现通过还是通过 setTimeout 来实现的。

let time heartbeatTimer () { time = setTimeout(() => { clearTimeout(time) // 开启下一次心跳 heartbeatTimer() // 心跳接口请求 heartbeatTimerApi() }, 10000) }

出现的问题

通过 setTimeout 后发自行测试的时候是实现了间隔10秒一次的心跳接口请求,但是测试人员发现,在线状态会在自己未做状态更改的时候被后台返回的 online 值给离线了。

解决问题

起初是从 Event Loop 角度出发,觉得可能是主应用可能是执行比较吃性能的任务,或者一直在页面上进行操作,导致事件循环中一直有同步代码压入调用栈,从而一直无法让消息队列的任务进入到调用栈执行。

后续发现页面未有任何操作的情况下也被后端下线了, 原因还是因为心跳保持59秒都没有发起请求的情况导致了后台认为当前工作人员以离线。

和测试的沟通描述到一个场景就是出于在线的工作人员状态的时候, 有时候会将浏览器先收起来最小化,或者切换到别的页签的场景。

配合浏览器任务管理器 + 描述的场景 = 结论

设定了定时器的页面被后台的时候(浏览器最小化, 页签不被激活),随着浏览器的本身的线程调度策略降低,导致页面上的定时器执行情况也被降低了,导致心跳接口直接变成近分钟才会执行一次,后台刚好50秒间隔扫描一次,_(:з」∠)_ 这个运气也是没谁了。

let time = Date.now() setInterval(() => { console.log('距离上一次执行相差', Date.now() - time) time = Date.now() }, 500)

let time = Date.now() function test() { timer = setTimeout(() => { test() const tempTime = Date.now() console.log('距离上一次执行的时间差:', tempTime - time) time = tempTime }, 500) } test()

诶, 要是我们通过 requestAnimationFrame 模拟 setTimeout 和 setInterval 呢 ?_(:з」∠)_

class RAF { constructor () { this.init() } init () { this._timerIdMap = { timeout: {}, interval: {} } } run (type = 'interval', cb, interval = 16.7) { const now = Date.now let stime = now() let etime = stime //创建Symbol类型作为key值,保证返回值的唯一性,用于清除定时器使用 const timerSymbol = Symbol() const loop = () => { this.setIdMap(timerSymbol, type, loop) etime = now() if (etime - stime >= interval) { if (type === 'interval') { stime = now() etime = stime } cb() type === 'timeout' && this.clearTimeout(timerSymbol) } } this.setIdMap(timerSymbol, type, loop) return timerSymbol // 返回Symbol保证每次调用setTimeout/setInterval返回值的唯一性 } setIdMap (timerSymbol, type, loop) { const id = requestAnimationFrame(loop) this._timerIdMap[type][timerSymbol]= id } setTimeout (cb, interval) { // 实现setTimeout 功能 return this.run('timeout', cb, interval) } clearTimeout (timer) { cancelAnimationFrame(this._timerIdMap.timeout[timer]) } setInterval (cb, interval) { // 实现setInterval功能 return this.run('interval', cb, interval) } clearInterval (timer) { cancelAnimationFrame(this._timerIdMap.interval[timer]) } } var raf = new RAF() var timer1 = raf.setInterval(() =>{ console.log(1000) }, 1000) var timer2 = raf.setInterval(() =>{ console.log(1500) }, 1500) raf.setTimeout(() => { raf.clearInterval(timer1) raf.clearInterval(timer2) }, 6000) 引用的原址: https://juejin.cn/post/6999444668089892901#heading-11

然后出现了更离谱的事,通过模拟的定时器,在页面切后台等操作的时候直接不会执行 233,

为什么? 因为切换到其他页面的时候,requestAnimationFrame 获取不到浏览器更新的频率,回调函数也就不执行了。

解决办法

通过 web Worker 来新开辟一条线程来执行定时器任务,这个时候选择 setTimeout,setInterval 就都是可以的了

不过 webWorker 必须与其创建者同源

之前是尝试过 worker.js 文件放置于项目的public文件夹打包的时候以静态方式来去读取,不过在线上环境,public文件下的文件都被上传到oss服务器去了,存在跨域,与非同源了都一个问题。放目录下又被webpack给编译掉了文件名称。

为了解决此问题,通过 Blob 将代码段生成临时的JS文件来进行访问,就避免了上述的问题。

function createWorker (f) { var blob = new Blob(['(' + f.toString() + ')()']) var url = window.URL.createObjectURL(blob) var worker = new Worker(url) return worker } createWorker(()=>{ let time = Date.now() setInterval(() => { const tempTime = Date.now() console.log('差值:', tempTime - time) time = tempTime }, 500) })

因此,以后大家在使用到前端需要轮训到定时任务到时候,推荐使用 worker 来实现,不用担心 Event Loop 的队列执行问题,也不用担心浏览器线程调度的问题。

小知识 _(:з」∠)_

教大家怎么在控制台辨别当前网页是否开启了子线程

如果启动的子线程有请求的话,在 Network 面板下 请求也会携带一个线程图标在前面



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3